package com.vislardo.app.notebook.utils;


import cn.hutool.core.util.StrUtil;
import com.vislardo.app.notebook.model.NoteInfo;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import java.util.stream.Stream;

/**
 *
 */
public class DataManager {

    private static volatile DataManager instance;

    // 加密密钥（示例中硬编码，实际应从安全位置获取）
    private static final String DEFAULT_ENCRYPTION_KEY = "vislardo@900318";
//    private static final String ENCRYPTION_KEY = System.getenv("VIS_NOTE_BOOK_ENC_KEY");

    private static final String DB_FILE_NAME = "noteinfo.vdb";
    private static final String ALGORITHM = "AES";

    private boolean db_file_exist;

    private boolean db_loadabel;


    //日记本数据
    private Map<String,NoteInfo> dataMap;

//    private String dataFilePath;
    private DataManager(){
        this.dataMap = new HashMap<>();
    }
    public static DataManager getInstance(){
        if(instance == null){
            synchronized (DataManager.class){
                if(instance == null){
                    instance = new DataManager();
                    instance.reloadData();
                }
            }
        }
        return instance;
    }

    /**
     * 加载数据
     */
    private boolean reloadData(){
        String dataFilePath = AppConfigManager.getInstance().getDataFolderPath();
        Path dataFile = Path.of(dataFilePath,DB_FILE_NAME);
        if(Files.exists(dataFile)) {
            db_file_exist = true;
            try (Stream<String> lines = Files.lines(dataFile)) {
                dataMap.clear();
                lines.forEach(line -> {
                    String line_decode = decrypt(line);
                    NoteInfo info = new NoteInfo(line_decode);
                    dataMap.put(info.getNoteCode(), info);
                });
                db_loadabel = true;
                return true;
            } catch (IOException e) {
                System.out.println(e.getMessage());
                db_loadabel = false;
            }
        }else{
            db_file_exist = false;
        }

        return false;
    }


    private Key getEncryptionKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
        String key = System.getenv("VIS_NOTE_BOOK_ENC_KEY");
        if(StrUtil.isBlank(key)) {key=DEFAULT_ENCRYPTION_KEY;}
        //使用密钥派生函数，将可能存在长度问题的密钥重新生成符合算法规定的密钥。
        byte[] salt = "daidai".getBytes();
        int keyLength = 256;

        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        PBEKeySpec spec = new PBEKeySpec(key.toCharArray(),salt,65536,keyLength);
        byte[] derivedKey = factory.generateSecret(spec).getEncoded();
        return new SecretKeySpec(derivedKey, ALGORITHM);

    }

    private String encrypt(String plainText) {
        try {
            Key key = getEncryptionKey();
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(encryptedBytes);
        } catch (Exception e) {
            System.out.println("加密操作失败");
            e.printStackTrace();
        }
        return "";
    }

    // 解密方法
    private String decrypt(String encryptedText) {
        try {
            Key key = getEncryptionKey();
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] decodedBytes = Base64.getDecoder().decode(encryptedText);
            byte[] decryptedBytes = cipher.doFinal(decodedBytes);
            return new String(decryptedBytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            System.out.println("解密操作失败");
            e.printStackTrace();
        }
        return "";
    }

    /**
     *
     * @param fileCode
     * @param key
     * @return
     */
    public Boolean saveSecrectKey(String fileCode, SecretKey key) {
        String encodeKey = Base64.getEncoder().encodeToString(key.getEncoded());
        NoteInfo info = dataMap.get(fileCode);
        info.setNewKey(encodeKey);
        dataMap.put(fileCode,info);
        new Thread(()->saveTofile()).start();
        return true;
    }

    private Boolean saveTofile(){

        Path tmp_path = Path.of(AppConfigManager.getInstance().getTempFolderPath(),DB_FILE_NAME);

       try(OutputStream os = Files.newOutputStream(tmp_path, StandardOpenOption.CREATE,StandardOpenOption.WRITE)){
           for (Map.Entry<String, NoteInfo> entryKey : dataMap.entrySet()) {
               NoteInfo info =dataMap.get(entryKey.getKey());
               if(info==null)continue;
               String data_line = encrypt(info.toString());
               os.write((data_line+"\n").getBytes(StandardCharsets.UTF_8));
           }
           Files.move(tmp_path,Path.of(AppConfigManager.getInstance().getDataFolderPath(),DB_FILE_NAME), StandardCopyOption.REPLACE_EXISTING);
           return true;
       }catch(IOException e){
           System.out.println("写入文件时出现IO错误"+"\n");
           e.printStackTrace();
       }finally {
           try {
               Files.deleteIfExists(tmp_path);
           } catch (IOException e) {
               System.out.println("删除临时文件时出现IO错误"+"\n");
               e.printStackTrace();
           }
       }
       return false;
    }

    public NoteInfo getNoteInfo(String code) {
        reloadData();
        return dataMap.get(code);
    }


    public void dbcheck() throws InterruptedException {
        if(db_file_exist){
            if(db_loadabel){
                System.out.println("数据文件加载成功，当前数据数："+dataMap.size());
            }else{
                System.out.println("数据文件加载失败……");
            }
        }else{
            System.out.println("数据文件不存在,自动创建,重新检测");
            saveNewBookInfo(NoteInfo.getTestNote());
            Thread.sleep(1000);
            reloadData();
            dbcheck();
        }
    }

    public void saveNewBookInfo(NoteInfo infoNew) {
        dataMap.put(infoNew.getNoteCode(),infoNew);
        new Thread(()->saveTofile()).start();
    }
}
